using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

using HowItWentDown.Enemies;

namespace HowItWentDown
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        public GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        /// <summary>
        /// The shader being used to draw to the screen
        /// </summary>
        public CustomEffect customEffect;
        public SpriteBatchEffects spriteEffect;
        Texture2D background;
        RenderTarget2D rendTarg1, rendTarg2, rendTarg3, rendTargBlur, rendTargBlur2, rendTargBloom, rendTargBloom2;
        public Matrix worldMatrix;
        Matrix viewMatrix;
        Matrix projectionMatrix;
        Controls controls;
        public List<Obj> objList;
        Texture2D healthBar;
        SpriteFont font;
        ShadowMonster testMonster;
        Texture2D shadowMonsterTexture;
        CageMonster testCageMonster;
        Matrix spriteMatrix;
        Crown crown;
        public Obj target; //the target of the Player
        public bool win = false;
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
            IsMouseVisible = true;
            graphics.ApplyChanges();
            Global.Doc = this;
            objList = new List<Obj>();
            Global.FPS = 60;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            base.Initialize();
            controls = new Controls();
            float tilt = MathHelper.ToRadians(0);  // 22.5 degree angle
            // Use the world matrix to tilt the cube along x and y axes.
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Load texture for background
            background = Global.LoadTexture("Clouds");
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            //Create Effect classes
            customEffect = new CustomEffect(GraphicsDevice);
            spriteEffect = new SpriteBatchEffects(GraphicsDevice);

            //determine graphics
            /*graphics.GraphicsDevice.BlendState = BlendState.AlphaBlend;
            graphics.PreferredBackBufferFormat = SurfaceFormat.Vector4;
            graphics.PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8;*/

            //create world, projection, and view coordinates
            worldMatrix = Matrix.CreateTranslation(new Vector3(0, 0, 200));
            viewMatrix = Matrix.CreateLookAt(new Vector3(0, 0, 0), new Vector3(0, 0, 50), Vector3.Up);
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(90), graphics.GraphicsDevice.Adapter.CurrentDisplayMode.AspectRatio, 1, 5000);
            customEffect.FogColor = Color.Black;

            int fieldSize = 5;
            for (int i = -5; i < fieldSize; i++)
            {
                for (int k = -5; k < fieldSize; k++)
                {
                    var tempObj = new Floor(this, 408 * i, 0, 408 * k, Global.LoadTexture("grass_tex_1024"));
                    tempObj.rot.X = 90;
                    tempObj.scale = 4;
                }
            }

            //tex = Global.LoadTexture("photo");
            for (int i = 0; i < 1600; i+=220)
            {
                for (int o = 0; o < 1600; o+=220)
                {
                    var obj = new Obj(this, i, 0, o, Global.LoadTexture("Tree"), true, true, true);
                    obj.scale = 1f;
                    obj.rect.TexPos = new Vector2(0, -0.24f);
                    obj.isTree = true;
                    //obj.rot.Y = i * 1;
                    //obj.velocity = new Vector3(3, 0, 0);

                }
            }
            for (int i = 0; i < 100; i++)
            {
                var obj = new PickUp(this, MyMath.RandomRange(0, 1000), 0, MyMath.RandomRange(0, 1000), Global.LoadTexture("Rock"));
                obj.bloom = 1;
                obj.scale = 0.1f;
            }
            for (int i = 0; i < 200; i++)
            {
                var c = new Cloud(this, MyMath.RandomRange(0, 1600), MyMath.RandomRange(15,20), MyMath.RandomRange(0, 1600));
            }
            crown = new Crown(this);
            //healthbar
            healthBar = Content.Load<Texture2D>("HealthBar_frame");
            font = Content.Load<SpriteFont>("gameFont");
            //the ground texture
            //var obj1 = new Obj(this, -50, 0, 0, Global.LoadTexture("TestGrass"));
            

            //var obj1 = new Obj(this, -50, 0, 0, Global.LoadTexture("grass_tex_1024"));
            //obj1.rot.X = 90; obj1.scale = 1;
            new Player(this, 800, 0, 200);

            new Companion(this, 850, 0, 100);
            //WorldObject is called the same as player but with the object type in front. //The WorldObject class is missing
            //new WorldObject("Rock", this, 0, 0, 100);
            Camera.LookAt = Camera.FollowObj = Global.Player;
            Camera.Pos = new Vector3(900, 100, 0);
            Camera.LookPos = Global.Player.pos;
            Console.WriteLine("Loaded");

            // Testing out the new Shadow Monster
            shadowMonsterTexture = Content.Load<Texture2D>("m2-2");
            for (int i = 0; i < 25; i++)
            {
                testMonster = new ShadowMonster(this, MyMath.RandomRange(200,1400), 25, MyMath.RandomRange(200,1400), shadowMonsterTexture, 10, 0.5f);
            }

            testCageMonster = new CageMonster(this, Global.Player.pos.X + 30, 0, Global.Player.pos.Z + 60);
            AddObj(testCageMonster);

            //GraphicsDevice.RasterizerState = new RasterizerState() { CullMode = CullMode.CullCounterClockwiseFace };
            //GraphicsDevice.DepthStencilState = new DepthStencilState() { DepthBufferWriteEnable = true, DepthBufferEnable = true };
            SetResolution(1280, 720);
            // Sets what effects will be used
            Global.Effects = Effects.Blur;
            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }
        public void SetResolution(int width, int height)
        {
            graphics.PreferredBackBufferWidth = width;
            Controls.ScreenSize.X = width;
            graphics.PreferredBackBufferHeight = height;
            Controls.ScreenSize.Y = height;
            rendTarg1 = new RenderTarget2D(GraphicsDevice, width, height);
            rendTarg2 = new RenderTarget2D(GraphicsDevice, width, height);
            rendTarg3 = new RenderTarget2D(GraphicsDevice, width, height);
            rendTargBlur = new RenderTarget2D(GraphicsDevice, width / 2, height / 2);
            rendTargBlur2 = new RenderTarget2D(GraphicsDevice, width / 2, height / 2);
        }
        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
            // TODO: Add your update logic here
            controls.update();
            //Camera.Pos += new Vector3(Controls.LeftStick, Controls.RightStick.Y);
            //Camera.LookAt = Global.Player;
            Global.Update(gameTime.ElapsedGameTime);
            Camera.Update();
            if (Controls.GetKey(Keys.LeftControl) == ControlState.Pressed)
            {
                if (!graphics.IsFullScreen)
                {
                    SetResolution(GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height);
                    graphics.IsFullScreen = true;
                    graphics.ApplyChanges();
                }
                else
                {
                    SetResolution(1280, 720);
                    graphics.IsFullScreen = false;
                    graphics.ApplyChanges();
                }
            }
            //testMonster.Update(gameTime.ElapsedGameTime.Milliseconds);
            float closestZ = float.MaxValue;

            for (int i = 0; i < objList.Count; i++)
            {
                objList[i].earlyUpdate();
            }
            for (int i = 0; i < objList.Count; i++)
            {
                //if targeted, highlight
                if (Controls.MouseLeft == ControlState.Pressed && objList[i].IsMouseOver())
                {
                    Obj t = null;
                    if (objList[i].pos.Z > Camera.Pos.Z + 30 && !(objList[i] is Player) && !(objList[i] is Companion) && !(objList[i] is ShadowBall) && !(objList[i] is Disappear) && !(objList[i] is Floor))
                    {
                        //closestZ = objList[i].pos.Z;
                        t = objList[i];
                    }
                    target = t;
                    if (t != null)
                    {
                        Console.WriteLine("Clicked on " + t.GetType());
                    }
                }
                objList[i].Update();
            }
            for (int i = 0; i < objList.Count; i++)
            {
                objList[i].lateUpdate();
            }
            SortObj();
            base.Update(gameTime);
        }
        public void AddObj(Obj o)
        {
            objList.Add(o);
        }
        public void RemoveObj(Obj o)
        {
            objList.Remove(o);
        }
        /// <summary>
        /// This method sorts objects based in on their distance from the camera.
        /// Objects further away are pushed to the beginning of the array, while objects closest to the camera are pushed to the end of the array
        /// The reason for this is so that objects are drawn in order. Objects farthest away will be drawn first, then the closer objects will be drawn on top
        /// This method doesn't yet properly work.
        /// </summary>
        private void SortObj()
        {
            float dist = 0, dist2 = 0;
            for (int i = 0; i < objList.Count; i++)
            {
                if (i > 0 && objList[i]!=null)
                {
                    if (objList[i].z > objList[i - 1].z && !(objList[i] is Floor))
                    {
                        //Console.WriteLine(i);
                        Obj temp = objList[i];
                        objList[i] = objList[i - 1];
                        objList[i - 1] = temp;
                        i -= 2;
                    }
                    
                }
            }
        }
        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            
            
            GraphicsDevice.BlendState = BlendState.AlphaBlend;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            if (rendTarg1 != null && rendTarg2 != null)
            {
                // Set the render targets on the graphics card. Instead of being rendered to the screen, the game will be rendered to these two textures
                switch (Global.Effects)
                {
                    default :
                        GraphicsDevice.SetRenderTarget(rendTarg1);
                        break;
                    case Effects.Blur:
                        GraphicsDevice.SetRenderTargets(rendTarg1, rendTarg2);
                        break;
                }
                GraphicsDevice.Clear(Color.Transparent);
            }
            
            customEffect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(Camera.FieldOfView), (float)graphics.PreferredBackBufferWidth / (float)graphics.PreferredBackBufferHeight, 1, 50000);
            customEffect.View = Matrix.CreateLookAt(Camera.Pos, Camera.LookPos, Vector3.Up);
            for (int i = 0; i < objList.Count; i++)
            {
                if (objList[i].visible)
                {
                    objList[i].Draw();
                }
            }
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            // reset the render target back to the screen
            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.Clear(Color.Black);
            #region Draw render targets to screen
            spriteEffect.Transform = Matrix.CreateTranslation(-0.5f, -0.5f, 0) * Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 0, 1);
            spriteEffect.DepthTexture = rendTarg2;
            // Draw the render target to the screen with the effects applied
            GraphicsDevice.SetRenderTarget(rendTargBlur);
            GraphicsDevice.Clear(Color.Transparent);
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.Default, null, spriteEffect);
            spriteEffect.CurrentTechnique = spriteEffect.Techniques["Normal"];
            spriteBatch.Draw(background, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
            spriteEffect.CurrentTechnique = spriteEffect.Techniques["BlurX"];
            spriteBatch.Draw(rendTarg1, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
            spriteEffect.CurrentTechnique = spriteEffect.Techniques["BlurY"];
            GraphicsDevice.SetRenderTarget(rendTargBlur2);
            GraphicsDevice.Clear(Color.Transparent);
            spriteBatch.Draw(rendTargBlur, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
            spriteBatch.End();
            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin();
            //draw background
            
            spriteBatch.Draw(rendTarg1, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
            spriteBatch.Draw(rendTargBlur2, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
            spriteBatch.End();
            #endregion
            spriteBatch.Begin();
            // Draw the health bar frame
            spriteBatch.Draw(healthBar, new Vector2(GraphicsDevice.Viewport.Width / 2 - healthBar.Width / 2, 10), new Rectangle(0, 49, healthBar.Width, 44), Color.Gray);
            // Add in the health
            spriteBatch.Draw(healthBar, new Vector2(GraphicsDevice.Viewport.Width / 2 - healthBar.Width / 2, 10), new Rectangle(0, 49, (int)(healthBar.Width * ((double)Global.Player.health / 100)), 44), Color.Red);
            // Draw the rectangular frame
            spriteBatch.Draw(healthBar, new Vector2(GraphicsDevice.Viewport.Width / 2 - healthBar.Width / 2, 10), new Rectangle(0, 0, healthBar.Width, 44), Color.White);
            // Draw the health text
            spriteBatch.DrawString(font, "Health: " + Global.Player.health.ToString() + " / 100", new Vector2(GraphicsDevice.Viewport.Width / 2  - 90, 18), Color.Black);
            // Draw hug counter
            spriteBatch.DrawString(font, "Hug Cooldown: " + Global.Companion.hugCooldown.ToString(), new Vector2(20, 20), Color.White);
            // Draw insult counter
            spriteBatch.DrawString(font, "Insult Cooldown: " + Global.Companion.insultCooldown.ToString(), new Vector2(20, 40), Color.White);
            spriteBatch.End();
            // TODO: Add your drawing code here
            base.Draw(gameTime);
        }

    }
}
